;;
;; utility functions are defined here
;;

(in-package :oali.proto)


(defmacro collect-cons (cons-targets &body body)
  "defines wrapper for easy-to-use list accumulators"
  `(let ,cons-targets
     (flet ,(loop for target in cons-targets collect
		 `(,target (arg) (setq ,target (append ,target (list arg)))))
       ,@body)))

;; breaks lambda list into it's components
;; the following values are returned:
;; [0] a list of all arguments (excluding keywords like &rest)
;; [1] a list of required args
;; [2] rest arg (if any)
;; [3] a list of optional args
(defun parse-lambda-list (lambda-list)
  "parses lambda list given"
  (collect-cons (all required)
    (let ((rest nil)
	  (state :required))
      ;; state might be only of the listed values
      (declare (type (member :required :rest :optional) state))
      
      (dolist (arg lambda-list)
	(if (member arg '(&rest &optional))
	    ;; {then} argument is a keyword specifier, state would be changed then
	    (case arg
	      (&optional
	       (unless (eq state :required)
		 (error 'internal-error :reason "misplaced &optional"))
	       (setq state :optional))
	      (&rest
	       (unless (member state '(:required :optional))
		 (error 'internal-error :reason "misplaced &rest"))
	       (setq state :rest))
	      (t (error 'not-implemented :reason "keyword unknown" :entity arg)))
	    ;; {else} treating another argument
	    (progn
	      (all arg)
	      (case state
		(:required (required arg))
		(:rest (if rest
			   (error 'internal-error :reason "two &rest arguments are not allowed")
			   (setq rest arg)))
		(t (error 'internal-error :reason "garbage in lambda list"))))))
      (if (and (eq state :rest) (null rest))
	  (error 'internal-error :reason "&rest without argument"))
      (values all required rest nil))))
